#include "MovingCameraByKeybord.h"
#include <GL/glext.h>
#include <cmath>
#include "GLFW/glfw3.h"

#include "glm/ext/matrix_transform.hpp"
#include "glm/geometric.hpp"
#include "glm/trigonometric.hpp"

MovingCameraByKeybord::MovingCameraByKeybord(const string& vertexShaderSource,
                                             const string& fragmentShaderSource,
                                             GLFWwindow* window)
    : MovingLocationShader(vertexShaderSource, fragmentShaderSource),
      window(window)
{
    cameraPosition = vec3(0.0f, 0.0f, 3.0f);
    cameraFront = vec3(0.0f, 0.0f, -1.0f);
    cameraUp = vec3(0.0f, 1.0f, 0.0f);

    /**
    We've set up the scene world so everything's positioned in the direction of
    the negative z-axis. However, if we look at the x and z yaw triangle we see
    that a θ of 0 results in the camera's direction vector to point towards the
    positive x-axis. To make sure the camera points towards the negative z-axis
    by default we can give the yaw a default value of a 90 degree clockwise
    rotation. Positive degrees rotate counter-clockwise so we set the default
    yaw value to
    */
    yaw = -90;
    pitch=0;

    /**
     * 初始化设置鼠标上一次的位置在屏幕的中央（屏幕分辨率是800*600）
     */
    lastMouseXPos = 400;
    lastMouseYPos = 300;

    /**
    First we will tell GLFW that it should hide the cursor and capture it.
    Capturing a cursor means that, once the application has focus, the mouse
    cursor stays within the center of the window (unless the application loses
    focus or quits).
    After this call, wherever we move the mouse it won't be visible and it
    should not leave the window
    */
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    /**
    To calculate the pitch and yaw values we need to tell GLFW to listen to
    mouse-movement events
    确保对象生命周期长于 GLFW 窗口
    */
    glfwSetWindowUserPointer(
        window, this); // 使用 glfwSetWindowUserPointer 存储对象指针
    glfwSetCursorPosCallback(
        window, mouse_callback); // 回调函数必须是静态的或非成员函数
}

/**
通过 GLFW
的用户指针机制将对象实例传递到静态回调函数中，然后在静态回调中调用实际的成员函数。

Here xpos and ypos represent the current mouse positions. As soon as we register
the callback function with GLFW each time the mouse moves, the mouse_callback
function is called:
*/
void MovingCameraByKeybord::mouse_callback(GLFWwindow* window, double xpos,
                                           double ypos)
{

    MovingCameraByKeybord* that =
        static_cast<MovingCameraByKeybord*>(glfwGetWindowUserPointer(window));
    that->mouseMoved(xpos, ypos);
}

void MovingCameraByKeybord::mouseMoved(double xpos, double ypos)
{
    if(isFirstMouseCallback){
        isFirstMouseCallback = false;
        lastMouseXPos = xpos;
        lastMouseYPos = ypos;
    }

    /*
    1、Calculate the mouse's offset since the last frame.
    2、Add the offset values to the camera's yaw and pitch values.
    3、Add some constraints to the minimum/maximum pitch values.
    4、Calculate the direction vector.
    */

    double dx = xpos - lastMouseXPos;
    double dy =  lastMouseYPos - ypos ;

    const float sensitivity = 0.001f;

    dx *= sensitivity;
    dy *= sensitivity;

    yaw += dx;
    pitch += dy;

    if (pitch > 89.0f)
    {
        pitch = 89.0f;
    }
    if (pitch < -89.0f)
    {
        pitch = -89.0f;
    }
    vec3 direction;
    direction.y = sin(radians(pitch));
    float pitchInXzPlane = cos(radians(pitch));
    /*
    cos(pitch) 的作用：
    它 缩放 XZ 平面上的分量，使得：

    当 pitch 增大（抬头/低头）时，XZ 平面上的分量减小（因为视角更垂直）。

    当 pitch = 0° 时，XZ 平面上的分量完全由 yaw 决定。

    sin(pitch) 的作用：
    它直接决定 Y 分量（垂直方向）。

    最终 direction 是一个单位向量（归一化），表示 3D 空间中的视角方向。
    */
    direction.x = cos(radians(yaw)) * pitchInXzPlane;
    direction.z = sin(radians(yaw)) * pitchInXzPlane;

    cameraFront = normalize(direction);

    lastMouseXPos = xpos;
    lastMouseYPos = ypos;
}

mat4 MovingCameraByKeybord::createViewMatrix()
{

    auto currentTime = glfwGetTime();

    auto dt = currentTime - lastRenderTime;

    const float cameraSpeed = 0.1f; // adjust accordingly
    float newLength = cameraSpeed * dt;

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
    {
        cameraPosition.z -= newLength;
    }
    else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
    {
        cameraPosition.z += newLength;
    }

    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
    {
        /**
        * 为什么向量叉积的结果要规范化
        Note that we normalize the resulting right vector. If we wouldn't
        normalize this vector, the resulting cross product may return
        differently sized vectors based on the cameraFront variable. If we would
        not normalize the vector we would move slow or fast based on the
        camera's orientation instead of at a consistent movement speed.
        */
        cameraPosition += normalize(cross(cameraFront, cameraUp)) * newLength;
    }
    else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
    {
        cameraPosition -= normalize(cross(cameraFront, cameraUp)) * newLength;
    }

    lastRenderTime = currentTime;
    return lookAt(
        cameraPosition,

        /**
          * 摄像机看向的点
          为什么 camera 看向的目标（center）是 cameraFront + cameraPosition
              这是为了确保无论相机怎么移动，相机看向的地方也跟着移动
          The direction is the current position + the direction vector we just
          defined. This ensures that however we move, the camera keeps looking
          at the target direction
          */
        cameraPosition + cameraFront,
        /*
        vector that represents the up vector in world space (the up
        vector we used for calculating the right vector).
        */
        cameraUp);
}